﻿namespace KeyStone.Ioc
{
   using System;
   using System.Collections.Generic;
   using System.Globalization;
   using System.Reflection;
   using System.Linq;
   using KeyStone.Ioc.Collections.Concurrent;
   using KeyStone.Ioc.Attributes;
   using KeyStone.Ioc.Exceptions;

   /// <summary>
   /// Class IocContainer.
   /// </summary>
   public class IocContainer : IIocContainer
   {
      /// <summary>
      /// The disposed
      /// </summary>
      private bool disposed = false;

      /// <summary>
      /// The internal container
      /// </summary>
      private static ObservableConcurrentDictionary<string, Registration> internalContainer;

      /// <summary>
      /// Initializes a new instance of the <see cref="IocContainer" /> class.
      /// </summary>
      public IocContainer()
      {
         internalContainer = new ObservableConcurrentDictionary<string, Registration>();
         RegisterInstance<IIocContainer>(this);
      }

      /// <summary>
      /// Gets or sets a value indicating whether a type that has not been previously registered should be registered if a user attempts to
      /// resolve an instance of it.
      /// </summary>
      /// <value><c>true</c> if true, any type that has not been registered will be auto regitered when it is requested; otherwise, <c>false</c>.</value>
      /// <remarks>
      /// If a type is requested from the container that does not exist in the container, having this flag set will indicate that 
      /// the type should be imported into the container and then resolved for the user.  All types resolved in this manner will 
      /// automatically have a PerInstance lifetime.
      /// </remarks>
      /// <example>
      /// <code>
      /// [Main.cs]
      /// IIocContainer container = new IocContainer();
      /// container.RegisterUnResolvedTypes = true;
      ///  
      /// var myInstance =  container.Resolve&lt;IMyType&gt;();
      /// 
      /// </code>
      /// </example>
      public bool RegisterUnResolvedTypes { get; set; }

      /// <summary>
      /// Finds the types of.
      /// </summary>
      /// <typeparam name="TAttributedType">The type of the attributed to find</typeparam>
      /// <returns>Returns a list of specified types from the current app domain</returns>
      private List<Type> FindTypesOf<TAttributedType>()
      {
         return(from assembly in AppDomain.CurrentDomain.GetAssemblies()
                      from type in assembly.GetTypes()
                      where Attribute.IsDefined(type, typeof(TAttributedType))
                      select type).ToList();
      }

      /// <summary>
      /// Scans the current AppDomain and registers any types that are attributed with the specified type
      /// </summary>
      /// <typeparam name="TAttribute">The attribute to find</typeparam>
      /// <example>
      /// <code>
      /// [Main.cs]
      /// IIocContainer container = new IocContainer();
      /// 
      /// container.RegisterTypesOf&lt;RegisteredDependency&gt;();
      /// 
      /// var myInstance =  container.Resolve&lt;IMyType&gt;();
      /// 
      /// [MyClass.cs]
      /// 
      /// [RegisteredDependency(Lifetime = PerInstance)]
      /// public class MyClass
      /// {
      ///   // .... implementation
      /// }
      /// </code>
      /// </example>
      public void RegisterTypesOf<TAttribute>() 
         where TAttribute : RegisteredDependencyAttribute
      {
         List<Type> types = FindTypesOf<TAttribute>();

         types.ForEach(t =>
         {
           
            Type[] interfaces = t.GetInterfaces();
            if (interfaces.Length > 0)
            {
               CreateRegistrationForUnknownType(t, interfaces);
            }
            else 
            {
               CreateRegistrationForUnknownType(t, null);  
            }
         });

      }

      private void CreateRegistrationForUnknownType(Type t, Type[] interfaces)
      {
         Lifetime componentLifeTime = Lifetime.PerInstance;

         object[] resolvedTypeAttributes = t.GetCustomAttributes(typeof(RegisteredDependencyAttribute), false);
         if (resolvedTypeAttributes.Length > 0)
         {
            RegisteredDependencyAttribute attribute = resolvedTypeAttributes[0] as RegisteredDependencyAttribute;
            if (attribute != null)
            {
               componentLifeTime = attribute.Lifetime;
            }
         }

         if (interfaces != null && interfaces.Length > 0)
         {
            Type firstInterface = interfaces[0];
            Register(firstInterface, t, componentLifeTime);
         }
         else
         {
            Register(t, componentLifeTime);
         }
      }

      /// <summary>
      /// Registers the specified instance
      /// </summary>
      /// <typeparam name="TType">The type of the t type.</typeparam>
      /// <param name="instance">The instance.</param>
      /// <example>
      /// <code>
      /// MyNewType mytype = new MyNewType();
      /// IIocContainer container = new IocContainer();
      /// 
      /// container.Register&lt;IMyType&gt;(Lifetime.PerInstance);
      /// 
      /// var myInstance =  container.Resolve&lt;MyNewType&gt;(mytype);
      /// </code>
      /// </example>
      public void RegisterInstance<TType>(TType instance)
      {
         Registration registration = new Registration
         {
            Implementation = typeof(TType),
            Interface = null,
            Instance = instance,
            ConstructorArguments = null,
            LifeTime = Lifetime.Singleton
         };

         if (!internalContainer.ContainsKey(registration.Implementation.Name))
         {
            internalContainer.Add(registration.Implementation.Name, registration);
         }
      }

      /// <summary>
      /// Registers and maps the specified interface to the concrete type.
      /// </summary>
      /// <param name="TInterface">The interface.</param>
      /// <param name="TType">Type to map.</param>
      /// <param name="life">The lifetime of the item being created.</param>
      /// <param name="constructorArgs">The constructor arguments.</param>
      public void Register(Type TInterface, Type TType, Lifetime life = Lifetime.PerInstance, params object[] constructorArgs)
      {
         Registration registration = new Registration
         {
            Implementation = TType,
            Interface = TInterface,
            ConstructorArguments = constructorArgs,
            LifeTime = life
         };

         if (!internalContainer.ContainsKey(TInterface.Name))
         {
            internalContainer.Add(TInterface.Name, registration);
         }
      }

      /// <summary>
      /// Registers the concrete type
      /// </summary>
      /// <param name="TType">Type to create</param>
      /// <param name="life">The lifetime of the item being created</param>
      /// <param name="constructorArgs">The constructor arguments.</param>
      public void Register(Type TType, Lifetime life = Lifetime.PerInstance, params object[] constructorArgs)
      {
         Registration registration = new Registration
         {
            Implementation = TType,
            Interface = null,
            ConstructorArguments = constructorArgs,
            LifeTime = life
         };

         if (!internalContainer.ContainsKey(TType.Name))
         {
            internalContainer.Add(TType.Name, registration);
         }
      }

      /// <summary>
      /// Registers the specified interface to the concrete type.
      /// </summary>
      /// <typeparam name="TType">Type to map.</typeparam>
      /// <param name="life">The lifetime of the item being created.</param>
      /// <param name="constructorArgs">The constructor arguments.</param>
      /// <example>
      /// <code>
      /// IIocContainer container = new IocContainer();
      /// 
      /// container.Register&lt;IMyType&gt;(Lifetime.PerInstance);
      /// 
      /// var myInstance =  container.Resolve&lt;IMyType&gt;();
      /// </code>
      /// </example>
      public void Register<TType>(Lifetime life = Lifetime.PerInstance, params object[] constructorArgs)
      {
         Registration registration = new Registration
         {
            Implementation = typeof(TType),
            Interface = null,
            ConstructorArguments = constructorArgs,
            LifeTime = life
         };

         if (!internalContainer.ContainsKey(typeof(TType).Name))
         {
            internalContainer.Add(typeof(TType).Name, registration);
         }
      }

      /// <summary>
      /// Registers and maps the specified interface to the concrete type.
      /// </summary>
      /// <typeparam name="TInterface">The interface.</typeparam>
      /// <typeparam name="TType">Type to map.</typeparam>
      /// <param name="life">The lifetime of the item being created.</param>
      /// <param name="constructorArgs">The constructor arguments.</param>
      /// <example>
      /// <code>
      /// IIocContainer container = new IocContainer();
      /// 
      /// container.Register&lt;IMyType, MyType&gt;(Lifetime.PerInstance);
      /// 
      /// var myInstance =  container.Resolve&lt;IMyType&gt;();
      /// </code>
      /// </example>
      public void Register<TInterface, TType>(Lifetime life = Lifetime.PerInstance, params object[] constructorArgs )
      {
         Registration registration = new Registration
         {
            Implementation = typeof (TType),
            Interface = typeof (TInterface),
            ConstructorArguments = constructorArgs,
            LifeTime = life
         };

         if (!internalContainer.ContainsKey(typeof (TInterface).Name))
         {
            internalContainer.Add(typeof(TInterface).Name, registration);
         }
      }
      /// <summary>
      /// Resolves the type arguments.
      /// </summary>
      /// <param name="registration">The registration.</param>
      /// <returns> <see cref="ObjectData"/> </returns>
      private ObjectData ResolveTypeArgs(Registration registration)
      {
         return new ObjectData
         {
            ConstructorArgs = ResolveConstructorArguments(registration),
            Properties      = ResolveProperties(registration)
         };
      }

      /// <summary>
      /// Internals the resolve.
      /// </summary>
      /// <param name="type">The type.</param>
      /// <returns>System.Object.</returns>
      /// <exception cref="KeyStone.Exceptions.TypeResolutionException">null
      /// or
      /// null
      /// or
      /// null</exception>
      private object InternalResolve(Type type)
      {
         string name = type.Name;
         object outputObject = null;
         Registration registration = null;
         object[] constructorArgs;
         Dictionary<string, object> properties;
         ObjectData constructorData = null;

         if (internalContainer.ContainsKey(name))
         {
            GetKnownRegistration(name, ref registration, ref constructorData);

            outputObject = registration.Instance;
         }
         else
         {
            if (RegisterUnResolvedTypes)
            {
               RegisterAndResolveUnknownType(type, ref outputObject, ref registration, ref constructorData);
            }
            else
            {
               throw new TypeResolutionException(string.Format("Unable to resolve '{0}'", type.Name), null);
            }
         }

         return outputObject;
      }

      /// <summary>
      /// Registers the type of the and resolve unknown.
      /// </summary>
      /// <param name="type">The type.</param>
      /// <param name="outputObject">The output object.</param>
      /// <param name="registration">The registration.</param>
      /// <param name="constructorData">The constructor data.</param>
      /// <exception cref="TypeResolutionException">
      /// thrown if a registered name cannot be resolved
      /// </exception>
      private void RegisterAndResolveUnknownType(Type type, ref object outputObject, ref Registration registration, ref ObjectData constructorData)
      {
         Type interfaceForUnknownType = InterfaceForUnknownType(type);
         string registeredName = string.Empty;
         if (internalContainer.ContainsKey(interfaceForUnknownType.Name))
         {
            registration = internalContainer[interfaceForUnknownType.Name];

            registeredName = RegisterUnknown(interfaceForUnknownType, type);
            if (string.IsNullOrEmpty(registeredName))
            {
               throw new TypeResolutionException(string.Format("Unable to resolve type with interface '{0}'", type.Name), null);
            }
         }
         else
         {
            registeredName = RegisterUnknown(null, type);
            if (string.IsNullOrEmpty(registeredName))
            {
               throw new TypeResolutionException(string.Format("Unable to resolve type with interface '{0}'", type.Name), null);
            }
         }


         registration = internalContainer[registeredName];

         if (!type.IsInterface)
         {
            constructorData = ResolveTypeArgs(registration);

            SetResolvedInstanceForLifeTime(ref registration, constructorData.ConstructorArgs, constructorData.Properties);

            outputObject = registration.Instance;
         }
         else
         {
            throw new TypeResolutionException(string.Format("Unable to resolve type with interface '{0}'", type.Name), null);
         }
      }

      /// <summary>
      /// Gets the known registration.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="registration">The registration.</param>
      /// <param name="constructorData">The constructor data.</param>
      private void GetKnownRegistration(string name, ref Registration registration, ref ObjectData constructorData)
      {
         registration = internalContainer[name];
         constructorData = ResolveTypeArgs(registration);

         if (registration.LifeTime == Lifetime.PerInstance)
         {
            var instance = registration.Instance as IDisposable;

            if (instance != null)
            {
               instance.Dispose();
            }

            registration.Instance = null;

            SetResolvedInstanceForLifeTime(ref registration, constructorData.ConstructorArgs, constructorData.Properties);
         }
         else
         {
            if (registration.Instance == null)
            {
               SetResolvedInstanceForLifeTime(ref registration, constructorData.ConstructorArgs, constructorData.Properties);
            }
         }
      }

      /// <summary>
      /// Registers the unknown.
      /// </summary>
      /// <param name="TInterface">The t interface.</param>
      /// <param name="TType">Type of the t.</param>
      /// <returns>System.String.</returns>
      private string RegisterUnknown(Type TInterface, Type TType)
      {
         if (TInterface == null)
         {
            if (!TType.IsInterface)
            {
               Register(TType, Lifetime.PerInstance);
               return TType.Name;
            }
         }
         else
         {
            Register(TInterface, TType, Lifetime.PerInstance);
            return TInterface.Name;
         }

         return string.Empty;
      }

      /// <summary>
      /// Sets the resolved instance for life time.
      /// </summary>
      /// <param name="registration">The registration.</param>
      /// <param name="constructorArgs">The constructor arguments.</param>
      /// <param name="properties">The properties.</param>
      private void SetResolvedInstanceForLifeTime(
         ref Registration registration, 
         object[] constructorArgs, 
         Dictionary<string, object> properties)
      {
         // constructor injection
         registration.Instance = Activator.CreateInstance(registration.Implementation, BindingFlags.CreateInstance, null, constructorArgs, CultureInfo.CurrentCulture);
  
         // now inject the properties that we could resolve.
         if (properties.Count > 0)
         {
            registration.Instance = SetPropertyValues(registration.Instance, properties);
         }
      }


      /// <summary>
      /// Resolves the constructor arguments.
      /// </summary>
      /// <param name="registration">The registration.</param>
      /// <returns>System.Object[].</returns>
      private object[] ResolveConstructorArguments(Registration registration)
      {
         object[] constructorArgs = new object[0];
         ConstructorInfo[] constructors = ConstructorsForType(registration.Implementation);
         foreach (ConstructorInfo constructor in constructors)
         {
            ParameterInfo[] parameters = constructor.GetParameters();
            foreach (ParameterInfo parameter in parameters)
            {
               Array.Resize(ref constructorArgs, constructorArgs.Length + 1);

               object resolvedDependency = Resolve(parameter.ParameterType);
               if (resolvedDependency != null)
               {
                  constructorArgs[constructorArgs.Length - 1] = resolvedDependency;
               }

            }
         }

         return constructorArgs;
      }

      /// <summary>
      /// Constructor arguments
      /// </summary>
      /// <param name="type">The type.</param>
      /// <returns>ConstructorInfo[].</returns>
      private ConstructorInfo[] ConstructorsForType(Type type)
      {
         return type.GetConstructors();
      }

      /// <summary>
      /// Interfaces the type of for unknown.
      /// </summary>
      /// <param name="type">The type.</param>
      /// <returns>Type.</returns>
      private Type InterfaceForUnknownType(Type type)
      {
         Type[] interfaces = type.GetInterfaces();
         if (interfaces.Length > 0)
         {
            return interfaces[0];
         }

         return null;
      }

      /// <summary>
      /// Resolves the properties.
      /// </summary>
      /// <param name="registration">The registration.</param>
      /// <returns>Dictionary&lt;System.String, System.Object&gt;.</returns>
      private Dictionary<string, object> ResolveProperties(Registration registration)
      {
         Dictionary<string, object> outputDictionary = new Dictionary<string, object>();
         PropertyInfo[] properties = registration.Implementation.GetProperties();

         foreach (PropertyInfo property in properties)
         {
            object[] dependencyAttributes = property.GetCustomAttributes(typeof (DependencyAttribute), false);

            if (dependencyAttributes.Length > 0)
            {
               object resolvedDependency = Resolve(property.PropertyType);

               if (resolvedDependency != null)
               {
                  outputDictionary.Add(property.Name, resolvedDependency);
               }
            }
         }

         return outputDictionary;
      }

      /// <summary>
      /// Sets the property values.
      /// </summary>
      /// <param name="instance">The instance.</param>
      /// <param name="dependencies">The dependencies.</param>
      /// <returns>System.Object.</returns>
      private object SetPropertyValues(object instance, Dictionary<string, object> dependencies)
      {
         PropertyInfo[] properties = instance.GetType().GetProperties();
         foreach (PropertyInfo property in properties)
         {
            if (dependencies.ContainsKey(property.Name))
            {

            object propertySetterValue = dependencies[property.Name];
            property.SetValue(instance, 
               propertySetterValue, 
               BindingFlags.SetProperty,
               null,
               null, 
               CultureInfo.CurrentCulture);
            }
         }

         return instance;
      }

      /// <summary>
      /// Resolves the specified type.
      /// </summary>
      /// <param name="type">The type.</param>
      /// <returns>System.Object.</returns>
      public object Resolve(Type type)
      {
         object outputObject = InternalResolve(type);

         return outputObject;
      }

      /// <summary>
      /// Registers the specified interface to the concrete type.
      /// </summary>
      /// <typeparam name="TType">Type to map.</typeparam>
      /// <param name="life">The lifetime of the item being created.</param>
      /// <param name="constructorArgs">The constructor arguments.</param>
      public TType Resolve<TType>() 
      {
         object outputObject = InternalResolve(typeof(TType));

         return (TType)outputObject;
      }

      /// <summary>
      /// Disposes this instance.
      /// </summary>
      public void Dispose()
      {
         Dispose(true);
         GC.SuppressFinalize(this);
      }

      // Protected implementation of Dispose pattern.
      /// <summary>
      /// Releases unmanaged and - optionally - managed resources.
      /// </summary>
      /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposed)
            return;

         if (disposing)
         {
            foreach (KeyValuePair<string, Registration> registration in internalContainer)
            {
               if (registration.Value.Instance != null && registration.Value.Instance is IDisposable)
               {
                  ((IDisposable)registration.Value.Instance).Dispose();
               }
            }

            internalContainer.Clear();
         }

      
         disposed = true;
      }
   }

}
